<?php

require_once 'util.php';
require_once 'ActionFramework/inputs.php';

/**
 * 
 * An action is a certain operation the user can request.
 * 
 * Examples:
 * 		- Log in
 * 		- View default page
 * 		- Store changed user profile
 * 		- ...
 * 
 * The user requests an action via the $_Request['action'] 
 * parameter (via GET or POST).
 * 
 * An action has certain inputs that must be present in
 * the request. These inputs are also checked for their
 * content, both server-side (php) and client-side 
 * (javascript).
 * 
 * An action definition consists of:
 * 	1. Definition of the required inputs (in __construct).
 * 	2. Definition of the required operations to be executed
 * 		when a request arrives (in trigger).
 * 
 * The action is used to handle the request, and supports
 * other actions/views when they want to link to this action.
 * 
 *  Example usage:
 *  
 *
 */
class Action {
	
	const INPUT_ACTION = 'action';
	
	/**
	 * Array that maps the input name (as shown in the HTML 
	 * and provided in the GET or POST request) to Input Objects.
	 */
	private $inputs = array();
	
	public function __construct() {
		$input = new Input(Action::INPUT_ACTION);
		$input->set_value($this->get_name());
		$this->add_input($input);
	}
	
	/**
	 * Add an input field
	 * 
	 * @param str $name Identifies this input (this will be the name of the form inputs and the name in the $_Request var
	 * @param str $js, $php An exppression  that evaluates to true (with eval) if the user-provided data is valid and 
	 * 					the form may be sent. The php key is most important for security. The js check should 
	 * 					match the php check as good as possible, for the best user experience, but is optional.
	 * @param str $comment Comment that explains the reason of failure of the check to the user 
	 * @param str $value The (default) value for this input
	 * 
	 * in js, php and comment, the following can be used:
	 *  	$value, which will be replaced by the actual input value.
	 *  	$alias, which will be replaced by the "nice name" of the input.
	 */
	public function add_input($name, $alias=null, $check=null) {
		assert(!is_array($alias));
		if ($name instanceof Input) {
			assert($alias==null);
			$input = $name;
		}
		else {
			$input = new Input($name, $alias, $check);
		}
		$this->inputs[$input->get_name()] = $input; 
	}
	
	/**
	 * Get the name of the action (simply the name of the class, without Action).
	 */
	public function get_name() {
		return str_replace('Action', '', get_class($this));
	}
	
	/**
	 * Return the link to this action as a GET-style url.
	 * 
	 * All elements must have a value attribute set!
	 */
	public function get_direct_link() {
		//assert($this->type == Action::TYPE_GET);
		return new DirectLink($this->inputs);
	}
	
	/**
	 * Return a Form container with all the required inputs.
	 */
	public function get_form_link() {
		return new Form($this->inputs);
	}
	
	/**
	 * Check that the data in the request is present and is
	 * valid. In that case, trigger this action.
	 */
	public function check_and_handle_request($request) {
		
		$unavailable = $this->get_unavailable_inputs($request);
		if (!empty($unavailable)) {
			$report = report_error("Insufficient data in request", debug_backtrace());
			return new RequestInsufficientView($unavailable, DEBUG? $report: '');
		}
		
		$checked_inputs = array();
		foreach ($this->inputs as $input_name => $input) {
			if ($input_name == 'action') {
				// Doesn't need checking; We are already in the Action!
				continue;
			}
			$result = $input->validate($request[$input_name]);
			if (!$result['valid']) {
				$report = report_error("Request invalid: " . $result['message'], debug_backtrace());
				return new RequestInvalidView($result['message'], DEBUG? $report: '');				
			}		
			$checked_inputs[$input_name] = $request[$input_name];
		}
		
		// All ok
		return $this->trigger($checked_inputs);
		
	}
	
	/**
	 * Check for the availability of all inputs.
	 */
	public function get_unavailable_inputs($request) {
		$unavailable = array();
		foreach ($this->inputs as $input_name => $input) {
			if (!array_key_exists($input_name, $request) and $input_name != Action::INPUT_ACTION) {
				$unavailable[$input_name] = $input;
			}
		}
		return $unavailable;
	}
	
	/**
	 * Triggered when a request has the required data available.
	 * 
	 * Returns a View object.
	 */
	public function trigger($request) {
		error("Should be overriden");
	}
	
}


class DirectLink {
	
	private $inputs;
	
	public function __construct($inputs) {
		$this->inputs = $inputs;
	}
	
	public function set_value($name, $value) {
		$this->inputs[$name]->set_value($value);
	}
	
	public function __toString() {
		$pairs = array();
		foreach ($this->inputs as $name => $input) {
			if ($input->get_value() == null) {
				throw new Exception("No value set for $name");
			}
			$pairs[] = $name.'='.$input->get_value();
		}
		return "?".implode("&",$pairs);
		
	}
	
	public function as_url() {
		return current_page_url() . $this->__toString();
	}
	
}



?>